import { DefaultPlugins } from "./plugins";
import type { ArtalkConfig, ArtalkPlugin, ContextApi } from "@/types";
import { handleConfFormServer } from "@/config";
import { showErrorDialog } from "@/components/error-dialog";

/**
 * Global Plugins for all Artalk instances
 */
export const GlobalPlugins: Set<ArtalkPlugin> = new Set([...DefaultPlugins]);

/**
 * Plugin options for plugin initialization
 */
export const PluginOptions: WeakMap<ArtalkPlugin, any> = new WeakMap();

export async function load(ctx: ContextApi) {
  const loadedPlugins = new Set<ArtalkPlugin>();
  const loadPlugins = (plugins: Set<ArtalkPlugin>) => {
    plugins.forEach((plugin) => {
      if (typeof plugin === "function" && !loadedPlugins.has(plugin)) {
        plugin(ctx, PluginOptions.get(plugin));
        loadedPlugins.add(plugin);
      }
    });
  };

  // Load local plugins
  loadPlugins(GlobalPlugins);

  // Get conf from server
  const data = {
    frontend_conf: {
      editorTravel: true,
      emoticons: "https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json",
      flatMode: "auto",
      gravatar: { mirror: "https://www.gravatar.com/avatar/", params: "sha256=1&d=mp&s=240" },
      heightLimit: { children: 400, content: 300, scrollable: false },
      imgLazyLoad: false,
      imgUpload: false,
      listSort: true,
      locale: "zh-CN",
      nestMax: 2,
      nestSort: "DATE_ASC",
      noComment: "",
      pagination: { autoLoad: true, pageSize: 20, readMore: true },
      placeholder: "",
      pluginURLs: [],
      preview: true,
      reqTimeout: 15000,
      sendBtn: "",
      uaBadge: false,
      versionCheck: false,
      vote: true,
      voteDown: false,
    },
  };

  // Initial config
  let conf: Partial<ArtalkConfig> = {
    // apiVersion: data.version?.version, // version info
  };

  // Reference conf from backend
  if (ctx.conf.useBackendConf) {
    if (!data.frontend_conf)
      throw new Error("The remote backend does not respond to the frontend conf, but `useBackendConf` conf is enabled");
    conf = { ...conf, ...handleConfFormServer(data.frontend_conf as any) };
  }

  // Apply conf modifier
  // ctx.conf.remoteConfModifier && ctx.conf.remoteConfModifier(conf);

  // Dynamically load network plugins
  conf.pluginURLs &&
    (await loadNetworkPlugins(conf.pluginURLs, ctx.conf.server)
      .then((plugins) => {
        loadPlugins(plugins);
      })
      .catch((err) => {
        console.error("Failed to load plugin", err);
      }));

  // After all plugins are loaded
  ctx.trigger("created");

  // Apply conf updating
  ctx.updateConf(conf);

  // Trigger mounted event
  ctx.trigger("mounted");

  // Load comment list
  if (!ctx.conf.remoteConfModifier) {
    // only auto fetch when no remoteConfModifier
    ctx.fetch({ offset: 0 });
  }
}

/**
 * Dynamically load plugins from Network
 */
async function loadNetworkPlugins(scripts: string[], apiBase: string): Promise<Set<ArtalkPlugin>> {
  const networkPlugins = new Set<ArtalkPlugin>();
  if (!scripts || !Array.isArray(scripts)) return networkPlugins;

  const tasks: Promise<void>[] = [];

  scripts.forEach((url) => {
    // check url valid
    if (!/^(http|https):\/\//.test(url)) url = `${apiBase.replace(/\/$/, "")}/${url.replace(/^\//, "")}`;

    tasks.push(
      new Promise<void>((resolve, reject) => {
        // check if loaded
        if (document.querySelector(`script[src="${url}"]`)) {
          resolve();
          return;
        }

        // load script
        const script = document.createElement("script");
        script.src = url;
        document.head.appendChild(script);
        script.onload = () => resolve();
        script.onerror = (err) => reject(err);
      })
    );
  });

  await Promise.all(tasks);

  // Read ArtalkPlugins object from window
  Object.values(window.ArtalkPlugins || {}).forEach((plugin) => {
    if (typeof plugin === "function") networkPlugins.add(plugin);
  });

  return networkPlugins;
}

export function onLoadErr(ctx: ContextApi, err: any) {
  let sidebarOpenView = "";

  // if response err_no_site, modify the sidebar open view to create site
  if (err.data?.err_no_site) {
    const viewLoadParam = {
      create_name: ctx.conf.site,
      create_urls: `${window.location.protocol}//${window.location.host}`,
    };
    sidebarOpenView = `sites|${JSON.stringify(viewLoadParam)}`;
  }

  showErrorDialog({
    $err: ctx.get("list").$el,
    errMsg: err.msg || String(err),
    errData: err.data,
    retryFn: () => load(ctx),
    onOpenSidebar: ctx.get("user").getData().isAdmin
      ? () =>
          ctx.showSidebar({
            view: sidebarOpenView as any,
          })
      : undefined, // only show open sidebar button when user is admin
  });
}
